package sonic.game.tile;
/**
 * 
 * 	Tile
 * 	By Richard Greenlees
 * 
 * 	The base class for all objects in the game including world geometry, items and game characters.
 * 	Static world geometry. As opposed to dynamic tiles which store their own animation instances,
 * 	Tile references a cached anim stored in the current GameMap when drawing to the screen.
 * 
 */

import java.awt.Graphics;
import java.io.File;
import java.io.IOException;

import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;

import sonic.game.Game;
import sonic.game.animation.Animation;
import sonic.game.util.Vector2D;



public class Tile {

	/** Current location of the tile. This is the bottom left corner */
	public Vector2D location = new Vector2D();
	
	public enum eCollisionType {CT_BLOCKALL, CT_TOUCHALL, CT_NONE};
	
	/**
	 * Determines the type of collision for a tile.<br>
	 * Possible Values:<p>
	 * 
	 * CT_BLOCKALL - Block movement of all other tiles, registers hitWall event<br>
	 * CT_TOUCHALL - Produces the touch event when coming into contact with other tiles, but will not block movement<br>
	 * CT_NONE - Allows everything to pass through without registering a hitWall or touch event
	 */
	public eCollisionType collisionType = eCollisionType.CT_BLOCKALL;
	
	public enum eRenderPriority {RP_BACKGROUND, RP_NORMAL, RP_FOREGROUND};
	
	/**
	 * Determines the priority of rendering for a tile
	 * Possible Values:<p>
	 * 
	 * RP_BACKGROUND - Will be hidden behind tiles with RP_NORMAL and RP_FOREGROUND<br>
	 * RP_NORMAL - Will be hidden behind RP_FOREGROUND but on top of RP_BACKGROUND. Default for player.<br>
	 * RP_FOREGROUND - Will always be rendered on top of everything else<p>
	 * 
	 * NB: The sky image is rendered separately and is always behind every tile
	 */
	public eRenderPriority renderPriority = eRenderPriority.RP_NORMAL;
	
	/** Current location of each corner of the tile. Starts at location (bottom left corner) at 0 and goes clockwise */
	public Vector2D[] tileCorners = {new Vector2D(), new Vector2D(), new Vector2D(), new Vector2D()};
	
	/** animSetsID and animSets are always in sync to allow for retrieval of an Animation using a search string */
	private String tileAnim;	
	
	/** If true, the game will permanently remove this Tile and all data from the game on the next loop */
	public boolean bDestroyMe = false;
	
	/** How long the tile should live for in seconds before being destroyed */
	public float lifeSpan = 0.0f;
	private float lifeTimer = 0.0f;
	
	/** If a tile is always relevant, it will always run its update method even when not on screen */
	public boolean bAlwaysRelevant = false;
	
	
	public Tile(Vector2D startPos)
	{
		setLocation(startPos);
	}
	
	public Tile()
	{
		
	}
	
	/** Determines if this tile needs to be updated and rendered based on whether it is on screen or not */
	public boolean isRelevant()
	{
		if (bAlwaysRelevant)
		{
			return true;
		}
		else
		{
			if (tileCorners[1].x < (Game.camera.location.x - 420) || tileCorners[0].x > (Game.camera.location.x + 420))
			{
				return false;
			}
			else if (tileCorners[2].y < (Game.camera.location.y - 340) || tileCorners[0].y > (Game.camera.location.y + 340) )
			{
				return false;
			}
		}
		
		return true;
	}
	
	public Tile(Vector2D startLocation, String animSetName)
	{
		tileAnim = animSetName;
		setLocation(startLocation);
	}
	
	public Tile(float startLocX, float startLocY, String animSetID)
	{
		tileAnim = animSetID;
		setLocation(startLocX, startLocY);
	}

	
	/** Flag this tile as ready for deletion */
	public void destroy()
	{
		bDestroyMe = true;
	}
	
	/** Returns a simple true/false if the supplied test point is encroaching on a tile */
	public boolean pointIsEncroaching(Vector2D testPoint)
	{
		for (Tile testTile : Game.currentMap.relevantTiles)
		{
			if (testTile == null || testTile.collisionType == eCollisionType.CT_NONE || testTile == this)
			{
				continue;
			}
			
			if (((testPoint.x > testTile.tileCorners[0].x) && (testPoint.x < testTile.tileCorners[1].x)) && ((testPoint.y > testTile.tileCorners[0].y) && (testPoint.y < testTile.tileCorners[3].y)))
			{
				return true;
			}
		}
		
		return false;
	}
	
	/** Return the point at the centre of the current tile */
	public Vector2D getCentre()
	{		
		Vector2D result = new Vector2D();
		result.x = tileCorners[0].x + (Game.currentMap.getCachedAnim(tileAnim).getCurrentFrame(false).getWidth()/2);
		result.y = tileCorners[0].y + (Game.currentMap.getCachedAnim(tileAnim).getCurrentFrame(false).getHeight()/2);
		return result;
	}
	
	/** Runs a trace from startPos to endPos, returning true if it didn't hit anything on the way */
	public boolean fastTrace(Vector2D startPos, Vector2D endPos, boolean bTraceTouching)
	{
		/*  This isn't the most elegant way of doing things as it rather laboriously checks every pixel
		 *  on the way. I should probably use a simpler check for intersection on the line. May do this
		 *  when I have time
		 */
		Vector2D currentPos = new Vector2D(startPos);
		
		while (currentPos.x != endPos.x || currentPos.y != endPos.y)
		{
			if (Math.abs(currentPos.x - endPos.x) < 1.0f)
			{
				currentPos.x = endPos.x;
			}
			
			if (Math.abs(currentPos.y - endPos.y) < 1.0f)
			{
				currentPos.y = endPos.y;
			}
			
			if (pointIsEncroaching(currentPos))
			{
				return false;
			}
			else
			{
				if (currentPos.x < endPos.x)
				{
					currentPos.x += 1.0f;
				}
				else if (currentPos.x > endPos.x)
				{
					currentPos.x -= 1.0f;
				}
				
				if (currentPos.y < endPos.y)
				{
					currentPos.y += 1.0f;
				}
				else if (currentPos.y > endPos.y)
				{
					currentPos.y -= 1.0f;
				}
			}
		}
		return true;
	}
	
	/** Similar to pointIsEncroaching(), but returns the encroached tile */
	public Tile checkEncroachingTile(Vector2D testPoint)
	{
		for (Tile testTile : Game.currentMap.relevantTiles)
		{
			if (testTile.collisionType == eCollisionType.CT_NONE)
			{
				continue;
			}
			
			if (((testPoint.x > testTile.tileCorners[0].x) && (testPoint.x < testTile.tileCorners[1].x)) && ((testPoint.y > testTile.tileCorners[0].y) && (testPoint.y < testTile.tileCorners[3].y)))
			{
				return testTile;
			}
		}
		
		return null;
	}
	
	/** All Tiles can in theory take damage from sources. Might be useful for interactive elements */
	public void takeDamage(Tile damager, int dmg)
	{
		
	}
	
	/** Use this to alter a tile's location, do not manually adjust location x or y or the collision bounds will not update properly */
	public void setLocation(Vector2D newLocation)
	{
		location.y = newLocation.y;
		location.x = newLocation.x;
		updateBounds();
	}
	
	/** Use this to alter a tile's location, do not manually adjust location x or y or the collision bounds will not update properly */
	public void setLocation(float newX, float newY)
	{
		location.y = newY;
		location.x = newX;
		updateBounds();
	}
	
	/** Will create a special effect at the designated position */
	public void createEffect(float xPos, float yPos, Animation effectAnim)
	{
		GameEffect newEffect = new GameEffect(xPos, yPos, effectAnim);
		Game.currentMap.pendingTiles.add(newEffect);
	}
	
	/** Touch event. Only called if this has CT_TOUCHALL and the touching tile has CT_TOUCHALL or CT_BLOCKALL */
	public void touch(Tile a)
	{

	}
	
	/** Updates the location of each corner of the tile's collision component. Used for collision detection */
	public void updateBounds()
	{
		if (Game.currentMap != null)
		{
			tileCorners[0] = new Vector2D(location.x, location.y);
			tileCorners[1] = new Vector2D(location.x + Game.currentMap.getCachedAnim(tileAnim).getCurrentFrame(false).getWidth(), location.y);
			tileCorners[2] = new Vector2D(location.x + Game.currentMap.getCachedAnim(tileAnim).getCurrentFrame(false).getWidth(), location.y + Game.currentMap.getCachedAnim(tileAnim).getCurrentFrame(false).getHeight());
			tileCorners[3] = new Vector2D(location.x, location.y + Game.currentMap.getCachedAnim(tileAnim).getCurrentFrame(false).getHeight());
		}
	}
	
	/** Each tile is responsible for drawing itself to the supplied Graphics object */
	public void paint(Graphics g)
	{
		g.drawImage(Game.currentMap.getCachedAnim(tileAnim).getCurrentFrame(false), ((int) location.x) + (320-(int) Game.camera.location.x), ((int) location.y) + (240-(int) Game.camera.location.y), null);
	}
	
	/** Every tile in the game has update called each frame */
	public void update(double deltaTime)
	{
		if (tileCorners[0].y > Game.currentMap.mapBoundsY)
		{
			destroy();
		}
		
		// If this tile has a limited lifespan then keep track and destroy when it expires
		if (lifeSpan > 0.0f)
		{
			lifeTimer += deltaTime;
			if (lifeTimer > lifeSpan)
			{
				destroy();
			}
		}
		updateBounds();
	}
	
	/** All tiles in the game can play sound effects */
	public void playSound(String fileName)
	{
        try {
            AudioInputStream audio = AudioSystem.getAudioInputStream(new File(fileName));
            Clip clip = AudioSystem.getClip();
            clip.loop(Clip.LOOP_CONTINUOUSLY);
            clip.open(audio);
            clip.start();
        }
        
        catch(UnsupportedAudioFileException uae) {
            System.out.println(uae);
        }
        catch(IOException ioe) {
            System.out.println(ioe);
        }
        catch(LineUnavailableException lua) {
            System.out.println(lua);
        }
	}

	
}
